package bgai.gamelogic;

import bgai.ai.minimax.BoardType;

/**
 * Funzioni di valutazione (euristiche) per backgammon.
 * <p>
 * Tutte le funzioni sono nell'intervallo [-1;1].
 * <p>
 * Vedi: ANDERS SJOQVIST and ANDRE STENLUND, "Constructing an Evaluation Function for Playing Backgammon"
 */
public class EvaluationFunctions {


	private static CheckerColor playerColor(BoardType type, boolean isMaxNode) {
		//if (isMaxNode) return BoardType.MAX.color();
		//else return BoardType.MIN.color();
		
		CheckerColor player= null;
		switch (type) {
		case MAX:
		case MIN:
			player= type.color();
			
			// verifico se "type" e' coerente con "isMaxNode"
			if (( type==BoardType.MAX && isMaxNode==false ) ||
				( type==BoardType.MIN && isMaxNode==true ) ) {
					throw new RuntimeException("Tipo di nodo non coerente con isMaxNode: nodo="+type+" ma isMaxNode="+isMaxNode);
			}
			break;

		case CHANCE:
			// scelgo MIN/MAX in base a isMaxNode
			if (isMaxNode) {
				player=BoardType.MAX.color();
			} else {
				player=BoardType.MIN.color();
			}
			
			break;
		}
		return player;
	}
	
	public static double winnerScore(boolean isMaxNode, boolean isMaxWin) {
		double e;
		
		if (isMaxNode && isMaxWin) e= 1;
		else if (isMaxNode && !isMaxWin) e= -1;
		else if (!isMaxNode && isMaxWin) e= 1;
		else e= -1; // if (!isMaxNode && !isMaxWin) 
		
		return e;
	}
	
	public static double hit1(Backgammon bg, BoardType type, boolean isMaxNode) {
		final double BOUND= 0.825;
		double e;
		
		CheckerColor player= playerColor(type, isMaxNode);
		
		if (bg.isTerminal()) {
			CheckerColor winCol= bg.winnerColor();
			boolean isMaxWin= BoardType.color2type(winCol)==BoardType.MAX;
			
			e= winnerScore(isMaxNode, isMaxWin);
			
		} else if (isMaxNode) {
			// calcolo per nodo MAX
			e= 0.55 * bg.getBarCheckers( player.opposite() );
			e= e/BOUND;
		} else {
			// calcolo per il nodo MIN
			e= - 0.55 * bg.getBarCheckers( player.opposite() );
			e= e/BOUND;
		}
		return e;
	}
	
	public static double hit2(Backgammon bg, BoardType type, boolean isMaxNode) {
		final double BOUND= 0.825;
		double e;
		
		CheckerColor player= playerColor(type, isMaxNode);
		
		if (bg.isTerminal()) {
			CheckerColor winCol= bg.winnerColor();
			boolean isMaxWin= BoardType.color2type(winCol)==BoardType.MAX;
			
			e= winnerScore(isMaxNode, isMaxWin);
			
		} else if (isMaxNode) {
			// calcolo per nodo MAX
			double diff= bg.getBarCheckers( player.opposite() );
			diff -= bg.getBarCheckers( player );
			e= 0.55 * diff;
			e= e/BOUND;
		} else {
			// calcolo per il nodo MIN
			double diff= bg.getBarCheckers( player.opposite() );
			diff -= bg.getBarCheckers( player );
			e= -0.55 * diff;
			e= e/BOUND;
		}
		
		return e;
	}
	
	public static double door(Backgammon bg, BoardType type, boolean isMaxNode) {
		final double BOUND= 0.35;
		double e;
		
		CheckerColor player= playerColor(type, isMaxNode);
		
		if (bg.isTerminal()) {
			CheckerColor winCol= bg.winnerColor();
			boolean isMaxWin= BoardType.color2type(winCol)==BoardType.MAX;
			
			e= winnerScore(isMaxNode, isMaxWin);
			
		} else if (isMaxNode) {
			// calcolo per nodo MAX
			int diff= doors( bg, player );
			diff -= doors( bg, player.opposite() );
			e= 0.05 * diff;
			e= e/BOUND;
		} else {
			// calcolo per il nodo MIN
			int diff= doors( bg, player );
			diff -= doors( bg, player.opposite() );
			e= -0.05 * diff;
			e= e/BOUND;
		}
		
		return e;
	}
	
	public static double doorHit1(Backgammon bg, BoardType type, boolean isMaxNode) {
		final double BOUND= 0.55375;
		double e;

		CheckerColor player= playerColor(type, isMaxNode);
		
		if (bg.isTerminal()) {
			CheckerColor winCol= bg.winnerColor();
			boolean isMaxWin= BoardType.color2type(winCol)==BoardType.MAX;
			
			e= winnerScore(isMaxNode, isMaxWin);
			
		} else {
			// calcolo per nodo MAX
			int deltaD= doors( bg, player );
			deltaD -= doors( bg, player.opposite() );
			
			int deltaH= bg.getBarCheckers( player.opposite() );
			deltaH-= bg.getBarCheckers( player );

			e= 0.25*(double)deltaD + 0.2525*(double)deltaH;
			
			if (!isMaxNode) {
				// calcolo per il nodo MIN
				e= -e;
			}
			e= e/BOUND;
		}

		
		return e;
	}
	
	public static double doorHit2(Backgammon bg, BoardType type, boolean isMaxNode) {
		final double BOUND= 0.635;
		
		double e;
		int n= 0;
		int m= 0;
	
		CheckerColor player= playerColor(type, isMaxNode);
		
		if (bg.isTerminal()) {
			CheckerColor winCol= bg.winnerColor();
			boolean isMaxWin= BoardType.color2type(winCol)==BoardType.MAX;
			
			e= winnerScore(isMaxNode, isMaxWin);
			
		} else {

			for (Point p : bg) {
				
				if (p.getNum()>=2 && p.getColor().equals(player)) {
					
					// door del colore di questo giocatore
					n+= 2; 
					
					// door nella home di questo giocatore
					if (p.isHome(player)) n+= 1; 
					
					// ha piu' di 4 checker
					if (p.getNum() > 4) n-= 1;
					
				} else if (p.getNum()>=2 && p.getColor().equals(player.opposite())) {
					
					// door del colore dell'avversario
					m-= 2; 
					
					// ha piu' di 4 checker
					if (p.getNum() > 4) m+= 1;
					
				}
				
			}
			
			int deltaH= bg.getBarCheckers( player.opposite() );
			deltaH-= bg.getBarCheckers( player );
			
			if (isMaxNode) {
				e= 0.01 * (n + m) + 0.029 * deltaH;
			} else {
				e= - 0.01 * (n + m) - 0.029 * deltaH;
			}
			
			e= e/BOUND; // normalizzo nel range [-1; +1]
		}
		return e;
	}
	
	public static double random(Backgammon bg, BoardType type, boolean prevIsMaxNode) {
		double e;
		
		// restituisco nell'intervallo [-1,+1]
		e= Math.round(Math.random()*2.0 - 1.0);
		
		return e;
	}
	
	/**
	 * Conta il numero di door, ovvero di point con due o piu' checker di un
	 * colore.
	 * @param bg
	 * @return
	 */
	private static int doors(Backgammon bg, CheckerColor color) {
		
		int d= 0;
		
		for (Point p : bg) {
			if (p.getNum()>=2 && p.getColor().equals(color)) {
				d++;
			}
		}
		
		return d;
	}
}
